using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using Sustainalytics.Entities;
using Sustainalytics.ESGRatings.Computing.Entities;
using Sustainalytics.ESGRatings.Computing.Entities.Governance;


// ReSharper disable PossibleNullReferenceException

namespace Sustainalytics.ESGComputing.Service.Tests.GovernanceUnitTests.Fixtures
{
    public abstract class BaseGovernanceFixture : IDisposable
    {
        protected readonly GovernanceHttpClient SutClient = new GovernanceHttpClient();


        public IEnumerable<Tuple<Guid, string, float>> GetIssuesWeights(string region, string issue)
        {
            foreach (var companyData in GetCompanies())
            {
                if (string.Equals(companyData.Region, region, StringComparison.InvariantCultureIgnoreCase))
                {
                    IssuesWeights firstOrDefault = GetWeightsMatrix().IssuesWeights.FirstOrDefault(x => string.Equals(x.Region, region, StringComparison.InvariantCultureIgnoreCase));
                    if (firstOrDefault != null)
                    {
                        IndicatorData<float> weight = firstOrDefault.Weights.FirstOrDefault(x => string.Equals(x.Code, issue, StringComparison.InvariantCultureIgnoreCase));
                        if (weight != null)
                        {
                            yield return new Tuple<Guid, string, float>(companyData.Id, weight.Code, weight.Value);
                        }
                    }
                }
            }
        }

        public IEnumerable<Tuple<Guid, int>> GetCompaniesCountInComparativeGroup(PerformanceType type)
        {
            var groupBy = type == PerformanceType.PeerGroup
                ? (Func<CompanyData, string>)(x => x.PeerGroup)
                : (x => x.Region);
            return GetCompanies().GroupBy(groupBy)
                .SelectMany(x => x, (c, g) => new Tuple<Guid, int>(g.Id, c.Count()));
        }

        protected abstract ICollection<CompanyData> GetCompanies();

        protected abstract RegionMatrix GetWeightsMatrix();

        protected abstract ICollection<CompanyScores> GetCompaniesRawScores();

        protected abstract ICollection<IndicatorTemplate> GetIndicatorTemplates();

        protected abstract ICollection<CombinationModifier> GetCombinationModifiers();

        public ICollection<Guid> GetCompaniesInRegion(string region)
        {
            return
                GetCompanies()
                    .Where(x => string.Equals(x.Region, region, StringComparison.InvariantCultureIgnoreCase))
                    .Select(x => x.Id).ToList();
        }

        public IEnumerable<Tuple<Guid, string, float>> ComputeIssuesScore(Guid companyId)
        {
            var companyRegion = GetCompanies().FirstOrDefault(x => x.Id == companyId).Region;
            var companyIndicatorDefaultScores = GetCompaniesRawScores().FirstOrDefault(x => x.CompanyId == companyId).IndicatorsData
                            .Select(x => new IndicatorData<float>(x.Code, (x.Value.RawScore - 50) / 50f)).ToList();

            var companyWeights =
                GetWeightsMatrix().Matrix.FirstOrDefault(x => string.Equals(x.EntityId, companyRegion, StringComparison.InvariantCultureIgnoreCase)).IndicatorsData;

            var indicatorTemplates = GetIndicatorTemplates();

            var weightedScores = (from companyWeight in companyWeights
                                  join companyIndicatorDefaultScore in companyIndicatorDefaultScores on companyWeight.Code equals companyIndicatorDefaultScore.Code
                                  select
                                      new Tuple<string, float, float>(companyWeight.Code, companyWeight.Value * companyIndicatorDefaultScore.Value,
                                          companyWeight.Value)).ToArray();

            var baseKeyIssue = from weightedScore in weightedScores
                               join indicatorTemplate in indicatorTemplates on weightedScore.Item1 equals indicatorTemplate.Code
                               group weightedScore by indicatorTemplate.Cluster
                                         into gj
                               select new IndicatorData<float>(gj.Key, gj.Sum(x => x.Item2) / gj.Sum(x => x.Item3));



            Debug.Write(baseKeyIssue.Dump());

            var combinationModifiers = GetCombinationModifiers();
            var weightedCombinationModifiers = new List<Tuple<string, string, float>>();



            foreach (var combinationModifier in combinationModifiers)
            {
                var weightedValue = 0f;
                if (combinationModifier.Weight > 0)
                {
                    var IndicatorDefaultScoreY = companyIndicatorDefaultScores.FirstOrDefault(x => string.Equals(x.Code, combinationModifier.IndicatorCodeY, StringComparison.InvariantCultureIgnoreCase));
                    var IndicatorDefaultScoreX = companyIndicatorDefaultScores.FirstOrDefault(x => string.Equals(x.Code, combinationModifier.IndicatorCodeX, StringComparison.InvariantCultureIgnoreCase));

                    if (IndicatorDefaultScoreX != null && IndicatorDefaultScoreY != null)
                    {
                        weightedValue = (IndicatorDefaultScoreX.Value + IndicatorDefaultScoreY.Value) * Math.Abs(IndicatorDefaultScoreY.Value) * Math.Abs(IndicatorDefaultScoreX.Value) * combinationModifier.Weight;
                        //(V4+$H4)*ABS($H4)*ABS(V4)*J4
                    }
                }
                weightedCombinationModifiers.Add(new Tuple<string, string, float>(combinationModifier.IndicatorCodeX, combinationModifier.IndicatorCodeY, weightedValue));

            }

            Debug.WriteLine(weightedCombinationModifiers.Dump());

            var groupedModifiers = from indicatorTemplate in indicatorTemplates
                                   join weightedCombinationModifier in weightedCombinationModifiers on indicatorTemplate.Code equals
                                       weightedCombinationModifier.Item1
                                       into joined
                                   from weightedCombinationModifier in joined.DefaultIfEmpty()
                                   group weightedCombinationModifier by indicatorTemplate.Cluster
                into gj
                                   select gj;

            var modifiers = from gj in groupedModifiers
                            select new IndicatorData<float>(gj.Key, gj.Sum(x => x == null ? 0 : x.Item3));

            Debug.Write(modifiers.Dump());

            var issueScores = (from modifier in modifiers
                               join weightedScore in baseKeyIssue on modifier.Code equals weightedScore.Code
                               let @score = (modifier.Value + weightedScore.Value) * 50 + 50
                               select new IndicatorData<float>(modifier.Code, @score)).ToList();

            return issueScores.Select(ws => new Tuple<Guid, string, float>(companyId, ws.Code, ws.Value));
        }

        public IEnumerable<Tuple<Guid, string, int>> ComputeRankingForKeyIssues(Guid companyId)
        {
            var companyRegion = GetCompanies().FirstOrDefault(x => x.Id == companyId).Region;
            var companiesInRegion = GetCompaniesInRegion(companyRegion);

            List<Tuple<Guid, string, float>> inGroupKeyIssueScores = new List<Tuple<Guid, string, float>>();

            foreach (var cId in companiesInRegion)
            {
                var keyIssuesScores = ComputeIssuesScore(cId);
                inGroupKeyIssueScores.AddRange(keyIssuesScores);

            }

            var computedRanking = from o in inGroupKeyIssueScores
                                  orderby o.Item3 descending
                                  select new
                                  {
                                      CompanyId = (from n in inGroupKeyIssueScores
                                                   where Math.Abs(n.Item3 - o.Item3) <= 1e-10 && n.Item1 == o.Item1 && n.Item2 == o.Item2
                                                   select n.Item1).FirstOrDefault(),
                                      Rating = o.Item3,
                                      Cluster = o.Item2,
                                      Position = inGroupKeyIssueScores.Count(s2 => s2.Item3 > o.Item3 && s2.Item2 == o.Item2) + 1
                                  };
            Debug.WriteLine(computedRanking.Dump());

            return computedRanking.Select(x => new Tuple<Guid, string, int>(x.CompanyId, x.Cluster, x.Position));

        }

        public Tuple<Guid, float> ComputeCompanyScore(Guid companyId)
        {
            var companyRegion = GetCompanies().FirstOrDefault(x => x.Id == companyId).Region;
            var issueScores = ComputeIssuesScore(companyId);
            float finalScore = (from score in issueScores
                                let issueWeight = GetIssuesWeights(companyRegion, score.Item2).FirstOrDefault(x => x.Item1 == companyId)
                                select score.Item3 * issueWeight.Item3).Sum();

            return new Tuple<Guid, float>(companyId, finalScore);
        }

        public abstract Guid SetId { get; }

        public abstract Guid MatrixId { get; }
        bool _disposed;

        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }

        ~BaseGovernanceFixture()
        {
            Dispose(false);
        }

        protected virtual void Dispose(bool disposing)
        {
            if (_disposed)
                return;

            if (disposing)
            {
                try
                {
                    var taskClean = SutClient.DeleteAsync(SetId);
                    taskClean.Result.EnsureSuccessStatusCode();
                }

                finally
                {
                    SutClient.Dispose();
                }
            }

            // release any unmanaged objects
            // set the object references to null

            _disposed = true;
        }
    }
}